use std::io::Read;

#[derive(PartialEq, PartialOrd, Clone, Copy)]
pub enum MessageType {
    Choke = 0,
    Unchoke,
    Interested,
    NotInterested,
    Have,
    Bitfield,
    Request,
    Piece,
    Cancel,
}

pub enum PayLoad {
    Choke,
    Unchoke,
    Interested,
    NotInterested,
    Have,
    Bitfield(Vec<u8>),
    Request(RequestPayLoad),
    Piece,
    Cancel,
}

pub struct RequestPayLoad {
    index: u32,
    offset: u32,
    length: u32,
}

pub struct Message {
    msg_type: MessageType,
    payload: Option<PayLoad>,
}

impl From<Message> for Vec<u8> {
    fn from(value: Message) -> Self {
        let mut res = Vec::<u8>::new();
        match &value.msg_type {
            MessageType::Choke
            | MessageType::Unchoke
            | MessageType::Interested
            | MessageType::NotInterested => {
                res = res
                    .iter()
                    .chain((1 as u32).to_be_bytes().as_slice())
                    .chain((value.msg_type as u8).to_be_bytes().as_slice())
                    .copied()
                    .collect();
            }
            MessageType::Have => todo!(),
            MessageType::Bitfield => {
                if let Some(PayLoad::Bitfield(field)) = value.payload {
                    res = res
                        .iter()
                        .chain(((1 + field.len()) as u32).to_be_bytes().as_slice())
                        .chain((value.msg_type as u8).to_be_bytes().as_slice())
                        .chain(&field)
                        .copied()
                        .collect();
                }
            }
            MessageType::Request => {
                if let Some(PayLoad::Request(req)) = value.payload {
                    res = res
                        .iter()
                        .chain((13 as u32).to_be_bytes().as_slice())
                        .chain((value.msg_type as u8).to_be_bytes().as_slice())
                        .chain(&req.index.to_be_bytes())
                        .chain(&req.offset.to_be_bytes())
                        .chain(&req.length.to_be_bytes())
                        .copied()
                        .collect();
                }
            }
            MessageType::Piece => todo!(),
            MessageType::Cancel => todo!(),
        }
        res
    }
}

impl Message {
    pub fn from_payload(payload: PayLoad) -> Self {
        match payload {
            PayLoad::Choke => Self {
                msg_type: MessageType::Choke,
                payload: None,
            },
            PayLoad::Unchoke => Self {
                msg_type: MessageType::Unchoke,
                payload: None,
            },
            PayLoad::Interested => Self {
                msg_type: MessageType::Interested,
                payload: None,
            },
            PayLoad::NotInterested => Self {
                msg_type: MessageType::NotInterested,
                payload: None,
            },
            PayLoad::Request(p) => Self {
                msg_type: MessageType::Request,
                payload: Some(PayLoad::Request(p)),
            },
            PayLoad::Bitfield(p) => Self {
                msg_type: MessageType::Bitfield,
                payload: Some(PayLoad::Bitfield(p)),
            },
            _ => Self {
                msg_type: MessageType::Choke,
                payload: None,
            },
        }
    }
}

impl RequestPayLoad {
    pub fn new(index: u32, offset: u32, length: u32) -> Self {
        Self {
            index,
            offset,
            length,
        }
    }
}
